#ifndef DDC_OBJ_H_INCLUDED
#define DDC_OBJ_H_INCLUDED

#include <string>
#include <vector>

#include "ddc_behavior.h"

typedef struct _DDC_Rect {
    int x, y;
    int width, height;
} DDC_Rect;

typedef struct _DDC_FRect {
    float x, y;
    float width, height;
} DDC_FRect;

typedef struct _DDC_Point2D {
    int x, y;
} DDC_Point2D;

DDC_FRect DDC_RectToFRect(DDC_Rect *rect);
DDC_Rect DDC_FRectToRect(DDC_FRect *rect);

class DDC_Vector2 {
public:
    double x, y;

    DDC_Vector2(int x = 0, int y = 0): x(x), y(y) {}

    static const DDC_Vector2 UP;
    static const DDC_Vector2 DOWN;
    static const DDC_Vector2 LEFT;
    static const DDC_Vector2 RIGHT;

    DDC_Point2D ToIntPoint();
};

/**
 * the representation of (a, b).
 * EMPTY when a > b.
 */
class DDC_Tuple2 {
public:
    double a, b;

    static const DDC_Tuple2 EMPTY;

    DDC_Tuple2(double a = 0, double b = 0): a(a), b(b) {}

    bool IsEmpty() {
        return a > b;
    }
};

class DDC_Component {
protected:
    DDC_GameObject *_attachedGameObject;

public:
    void __AttachGameObject(DDC_GameObject *go) {
        _attachedGameObject = go;
    }

    DDC_GameObject *GetAttachedGameObject() {
        return _attachedGameObject;
    }

    virtual int GetComponentID() {
        return 1;
    }

    virtual void Update() {};
};

class DDC_Scene;

class DDC_GameObject {
protected:
    DDC_Scene *_attachedScene = nullptr;

    std::vector<DDC_Behavior*> _behaviors;
    std::vector<DDC_GameObject*> _subObjects;
    std::vector<DDC_Component*> _components;

public:
    std::string name;
    DDC_GameObject(std::string name): name(name) {}

    std::vector<DDC_Behavior*>& GetBehaviors();
    std::vector<DDC_GameObject*>& GetSubObjects();
    std::vector<DDC_Component*>& GetComponents();

    template<typename T>
    T* AddBehavior() {
        T* t = new T;
        _behaviors.push_back(t);
        t->__AttachGameObject(this);
        return t;
    }

    DDC_GameObject *NewSubGameObject(std::string name, int *outIndex);
    DDC_GameObject *NewSubGameObject(std::string name);

    template <typename T>
    T *GetComponentByID(const int id)  {
        for (DDC_Component *cpt: _components) {
            if (cpt->GetComponentID() == id)
                return dynamic_cast<T*>(cpt);
        }
        return nullptr;
    }

    template <typename T>
    T *GetBehavior() {
        for (DDC_Behavior *bh: _behaviors) {
            T *t = dynamic_cast<T*>(bh);
            if (t != nullptr)
                return t;
        }
        return nullptr;
    }

    template <typename T>
    T *AddCompoment() {
        T* t = new T;
        _components.push_back(t);
        t->__AttachGameObject(this);
        return t;
    }

    void __AttachScene(DDC_Scene *scene) {
        _attachedScene = scene;
    }

    DDC_Scene *GetAttachedScene() {
        return _attachedScene;
    }

    DDC_GameObject *SetGameObjectRect(DDC_FRect rect) {
        gameObjectRect = rect;
        return this;
    }

    bool visable = true;
    DDC_FRect gameObjectRect = {};
    double rotationAngle = 0;
    DDC_Vector2 anchor = {0, 0};  /* the relation point for rotation
                            (x, y) which 0 <= x, y <= 1*/

    ~DDC_GameObject();
};

class DDC_Scene {
private:
    std::vector<DDC_GameObject*> _gameObjects;

    DDC_GameObject *_worldCamera;

public:
    int screenLogicalSizeWidth = -1, screenLogicalSizeHeight = -1;

    std::string name;

    DDC_Scene(std::string name);

    DDC_GameObject *NewGameObject(std::string name, int *outIndex);
    DDC_GameObject *NewGameObject(std::string name);
    /**
     * :return: > 0 if error occured.
     */
    bool RemoveGameObject(int index, bool deleteIt = true);

    DDC_GameObject *GetGameObejct(int index);
    DDC_GameObject *GetGameObejct(std::string name);

    void OnStart();
    void Update();

    DDC_GameObject *GetWorldCamera() {
        return _worldCamera;
    }

    void WorldTransformToCamera(const DDC_FRect *goRect,
                                const double goRotAngle,
                                const DDC_Vector2*goAnchor,
                                DDC_FRect *outRect,
                                double *outRotAngle,
                                DDC_Vector2 *outAnchor);
};

#endif // DDC_OBJ_H_INCLUDED
